package uk.ac.lkl.lpp.model.xml;



import java.util.ArrayList;
import java.util.Collection;
import java.util.logging.Logger;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import uk.ac.lkl.common.associator.Associator;
import uk.ac.lkl.common.associator.AssociatorHandle;

import uk.ac.lkl.common.util.ClassTriple;
import uk.ac.lkl.common.util.Converter;
import uk.ac.lkl.common.util.ParsingStringConverter;

import uk.ac.lkl.common.util.ds.AbstractNotifyingList;
import uk.ac.lkl.common.util.ds.NotifyingList;

import uk.ac.lkl.lpp.model.PedagogyPlanModel;
import uk.ac.lkl.lpp.ui.BackCompat;


public class AssociationSetProcessor extends ModelElementProcessor {

    private static final Logger logger = Logger.getLogger("uk.ac.lkl.lpp");


    public AssociationSetProcessor() {
        super("AssociationSet");
    }


    public void convertDataToElements(PedagogyPlanModel plan,
                                      Document document,
                                      Element pedagogyPlanElement) {
        // todo: since can be arbitrarily many of these, need
        // different processing to other processors really.

        Collection<AssociatorHandle> associatorHandles = plan
                .getAssociatorClassTriples();

        for (AssociatorHandle associatorHandle : associatorHandles) {
            Element associationSetElement = document
                    .createElement("AssociationSet");
            pedagogyPlanElement.appendChild(associationSetElement);

            associationSetElement.setAttribute("sourceClass", associatorHandle
                    .getLeftListClassName());

            String sourceListName = associatorHandle.getLeftListName();
            if (sourceListName != null)
                associationSetElement.setAttribute(
                                                   "sourceListName",
                                                   sourceListName);

            String annotationClassName = associatorHandle
                    .getAnnotationClassName();
            if (annotationClassName != null)
                associationSetElement.setAttribute(
                                                   "annotationClass",
                                                   annotationClassName);
            associationSetElement
                    .setAttribute("destinationClass", associatorHandle
                            .getRightListClassName());

            String destinationListName = associatorHandle.getRightListName();
            if (destinationListName != null)
                associationSetElement.setAttribute(
                                                   "destinationListName",
                                                   destinationListName);
           	associationSetElement.setAttribute("index",
                        						String.valueOf(associatorHandle.getUIIndex()));
            
            Associator associator = plan.getAssociator(associatorHandle);

            AbstractNotifyingList leftList = associator.getLeftList();
            AbstractNotifyingList rightList = associator.getRightList();

            for (int leftIndex = 0; leftIndex < leftList.size(); leftIndex++) {
                Object leftObject = leftList.get(leftIndex);
                ArrayList associations = associator
                        .getRightAssociations(leftObject);

                // skip if leftObject has no associations
                if (associations == null)
                    continue;

                for (Object rightObject : associations) {
                    Element associationElement = document
                            .createElement("Association");
                    // hack: associator should use indices really
                    int rightIndex = rightList.indexOf(rightObject);
                    associationElement.setAttribute("sourceIndex", Integer
                            .toString(leftIndex));

                    if (annotationClassName != null) {
                        Object annotation = associator
                                .getAnnotation(leftObject, rightObject);
                        if (annotation != null)
                            associationElement
                                    .setAttribute("annotation", annotation
                                            .toString());
                    }

                    associationElement.setAttribute("destinationIndex", Integer
                            .toString(rightIndex));
                    associationSetElement.appendChild(associationElement);
                }
            }
        }
    }


    public void convertElementToData(PedagogyPlanModel plan, Element element) {
        try {
            String sourceClassName = element.getAttribute("sourceClass");
            String annotationClassName = element
                    .getAttribute("annotationClass");
            String destinationClassName = element
                    .getAttribute("destinationClass");

            String sourceListName = element.getAttribute("sourceListName");
            String destinationListName = element
                    .getAttribute("destinationListName");
            
            // hack: get the "edit" param to see if this should be displayed on the 
            // linking panel. 
            // this should be part of the view, irrespective of the model, but that
            // too complicated at the moment
            String uiIndexVal = element.getAttribute("index");
            int uiIndex = -1;
            if (uiIndexVal != null) {
            	try {
            		uiIndex = Integer.parseInt(uiIndexVal);
            	} catch (NumberFormatException e){
            		logger.info("failed to get AssociationSet index: "+
            				sourceClassName+" to "+destinationClassName
            				+" by "+annotationClassName+", "+uiIndexVal);
            	}
            }


            // hack to convert pre 0.72 data files
            if (BackCompat.isOldForm(sourceClassName))
                sourceClassName = BackCompat.upgrade(sourceClassName);
            if (BackCompat.isOldForm(annotationClassName))
                annotationClassName = BackCompat.upgrade(annotationClassName);
            if (BackCompat.isOldForm(destinationClassName))
                destinationClassName = BackCompat.upgrade(destinationClassName);

            Class sourceClass = Class.forName(sourceClassName);
            Class annotationClass = annotationClassName.equals("") ? null
                    : Class.forName(annotationClassName);
            Class destinationClass = Class.forName(destinationClassName);

            AbstractNotifyingList sourceList = plan.getList(
                                                            sourceListName,
                                                            sourceClass);
            AbstractNotifyingList destinationList = plan
                    .getList(destinationListName, destinationClass);

            Associator associator = new Associator(sourceList, destinationList);

            // todo: allow for another attribute to specify how to
            // build instances of annotation from string
            Converter converter;
            if (annotationClass == null)
                converter = null;
            else {
                converter = new ParsingStringConverter(annotationClass);
            }

            NodeList associations = element.getElementsByTagName("Association");
            int numAssociations = associations.getLength();
            for (int i = 0; i < numAssociations; i++) {
                Node association = associations.item(i);

                // try {
                processAssociation(associator, (Element) association, converter);
                // } catch (IndexOutOfBoundsException e) {
                // logger.severe("too many associators! "+associator+";
                // "+association + e);
                // } catch(NullPointerException e){
                // logger.info("association["+i+"):"+association);
                // }
            }
            

            plan.addAssociator(
                               sourceListName,
                               sourceClass,
                               annotationClass,
                               destinationListName,
                               destinationClass,
                               associator,
                               uiIndex);
        }
        catch (ClassNotFoundException e) {
            // hack! Do more here!
            e.printStackTrace();
        }
    }


    private void processAssociation(Associator associator,
                                    Element association,
                                    Converter<String, ?> converter) {
        try {
            String sourceIndexString = association.getAttribute("sourceIndex");
            String destinationIndexString = association
                    .getAttribute("destinationIndex");
            String annotationString = association.getAttribute("annotation");

            int sourceIndex = Integer.parseInt(sourceIndexString);
            if (sourceIndex < 0) {
                // todo: add warning and ignore this association
            }

            int destinationIndex = Integer.parseInt(destinationIndexString);
            if (destinationIndex < 0) {
                // todo: add warning and ignore this association
            }

            if (annotationString.equals(""))
                associator.addAssociation(sourceIndex, destinationIndex);
            else {
                if (converter != null) {
                    Object annotation = converter.convert(annotationString);
                    associator.addAssociation(
                                              sourceIndex,
                                              annotation,
                                              destinationIndex);
                }
            }
        }
        catch (NumberFormatException e) {
            e.printStackTrace();
            // todo: add to list of warnings
        }
    }

}
